﻿/*************************************************************************************
 * 文 件 名:   HK_SmartCopy
 * 
 * 描    述:   对选中的物体进行智能复制，按Ctrl+Shift+D进行复制（只能且必须选中一个物体），
 *             第一次复制时会记录便宜的位移、旋转、缩放，之后再次按Ctrl+Shift+D可根据偏移量增量连续进行复制
 *             借鉴Maya中的复制功能）
 * 
 * 版    本：  V1.2
 * 创 建 者：  京产肠饭
 * 创建时间：  2021/8/26
 * 链   接：  https://gitee.com/JingChanChangFan/hk_-unity-tools
*************************************************************************************/

using UnityEditor;
using UnityEngine;

namespace HKTools
{
    [InitializeOnLoad]
    public class HK_SmartCopy
    {
        public const int SmartCopyPriority = 0; // HK_SmartCopy 排序

        // 执行的步骤
        static int moveStep = 0;

        // 选中的第一个物体 初始位置、旋转、缩放
        static Vector3 startPos, startEuler, startScale;
        static Vector3 firstForward, firstRight, firstUp;

        //通过复制出来的第一个物体 计算出来的 偏移位置、旋转、缩放
        static Vector3 offsetPos, offsetX, offsetY, offsetZ;
        static float offsetXVal, offsetYVal, offsetZVal;
        static Vector3 offsetEuler;
        static float xScaleIndex, yScaleIndex, zScaleIndex;

        //记录上一个复制的 位置和旋转
        static Vector3 lastPos;
        static Quaternion lastRotation;
        static Vector3 lastScale;

        static GameObject curSelectGo;

        // 验证方法，设置该工具是否启用
        [MenuItem("HKTools/SmartCopy(智能复制) %#D", true, SmartCopyPriority)]
        static bool CopyObjValidate()
        {
            return HK_ToolStates.IsActiveSmartCopy;
        }

        [MenuItem("HKTools/SmartCopy(智能复制) %#D", priority = SmartCopyPriority)]
        static void CopyObj()
        {
            // 判断是否只选中了一个物体 
            if (Selection.gameObjects.Length > 1)
            {
                Debug.LogError("HK_SmartCopy：只能选择一个Hierarchy中的物体进行复制!");
                return;
            }
            if (Selection.gameObjects.Length == 0)
            {
                Debug.LogError("HK_SmartCopy：未选中物体，无法执行复制操作!");
                return;
            }

            switch (moveStep)
            {
                case 0:
                    // 第一步：记录是否有 Parent，Copy选中物体，记录第一个物体 三个轴向 和 初始位置旋转
                    InitialStep();
                    break;
                case 1:
                    // 第二步 ： 判定 偏移向量 在各个轴向的分量，并复制
                    RecordOffset();
                    break;
                case 2:
                    // 第三步：就是一直复制呗
                    IncrementalCopy();
                    break;
            }
        }

        static void InitialStep()
        {
            EditorApplication.ExecuteMenuItem("Edit/Duplicate");

            curSelectGo = Selection.activeGameObject;

            //记录第一个物体的 三个轴向 & 开始的 位置和旋转
            firstForward = curSelectGo.transform.forward;
            firstRight = curSelectGo.transform.right;
            firstUp = curSelectGo.transform.up;

            startPos = curSelectGo.transform.position;
            startEuler = curSelectGo.transform.eulerAngles;
            startScale = curSelectGo.transform.localScale;

            moveStep++;
        }

        static void RecordOffset()
        {
            // 如果换了选中物体，或者选中物体位置与旋转没发生改变，则返回第一步
            if (Selection.activeGameObject != curSelectGo || (Selection.activeGameObject.transform.position == startPos && Selection.activeGameObject.transform.eulerAngles == startEuler && Selection.activeGameObject.transform.localScale == startScale))
            {
                moveStep = 0;
                CopyObj();
                return;
            }

            //计算便宜的向量 和 每个轴向的分量
            offsetPos = Selection.activeGameObject.transform.position - startPos;

            offsetX = Vector3.Project(offsetPos, firstRight);
            offsetY = Vector3.Project(offsetPos, firstUp);
            offsetZ = Vector3.Project(offsetPos, firstForward);

            if (Vector3.Dot(firstRight.normalized, offsetX.normalized) > 0)
                offsetXVal = offsetX.magnitude;
            else
                offsetXVal = -offsetX.magnitude;
            if (Vector3.Dot(firstForward.normalized, offsetZ.normalized) < 0)
                offsetZVal = -offsetZ.magnitude;
            else
                offsetZVal = offsetZ.magnitude;
            if (Vector3.Dot(firstUp.normalized, offsetY.normalized) < 0)
                offsetYVal = -offsetY.magnitude;
            else
                offsetYVal = offsetY.magnitude;

            offsetEuler = Selection.activeGameObject.transform.eulerAngles - startEuler;

            EditorApplication.ExecuteMenuItem("Edit/Duplicate");

            Selection.activeGameObject.transform.Translate(offsetXVal, offsetYVal, offsetZVal, Space.Self);
            Selection.activeGameObject.transform.eulerAngles = Selection.activeGameObject.transform.eulerAngles + offsetEuler;

            lastPos = Selection.activeGameObject.transform.position;
            lastRotation = Selection.activeGameObject.transform.rotation;

            xScaleIndex = Selection.activeGameObject.transform.localScale.x / startScale.x;
            yScaleIndex = Selection.activeGameObject.transform.localScale.y / startScale.y;
            zScaleIndex = Selection.activeGameObject.transform.localScale.z / startScale.z;

            float x = Selection.activeGameObject.transform.localScale.x * xScaleIndex;
            float y = Selection.activeGameObject.transform.localScale.y * yScaleIndex;
            float z = Selection.activeGameObject.transform.localScale.z * zScaleIndex;

            Selection.activeGameObject.transform.localScale = new Vector3(x, y, z);

            lastScale = Selection.activeGameObject.transform.localScale;

            curSelectGo = Selection.activeGameObject;

            moveStep++;
        }

        static void IncrementalCopy()
        {
            if (Selection.activeGameObject != curSelectGo)
            {
                moveStep = 0;
                CopyObj();
                return;
            }

            if (Vector3.Distance(Selection.activeGameObject.transform.position, lastPos) > 0.01f
                || Quaternion.Angle(Selection.activeGameObject.transform.rotation, lastRotation) > 0.1f
                || Vector3.Distance(Selection.activeGameObject.transform.localScale, lastScale) > 0.01f)
            {
                Debug.Log("重新复制");
                moveStep = 0;
                CopyObj();
                return;
            }

            EditorApplication.ExecuteMenuItem("Edit/Duplicate");

            Selection.activeGameObject.transform.Translate(offsetXVal, offsetYVal, offsetZVal, Space.Self);
            Selection.activeGameObject.transform.eulerAngles = Selection.activeGameObject.transform.eulerAngles + offsetEuler;

            lastPos = Selection.activeGameObject.transform.position;
            lastRotation = Selection.activeGameObject.transform.rotation;

            float x = Selection.activeGameObject.transform.localScale.x * xScaleIndex;
            float y = Selection.activeGameObject.transform.localScale.y * yScaleIndex;
            float z = Selection.activeGameObject.transform.localScale.z * zScaleIndex;

            Selection.activeGameObject.transform.localScale = new Vector3(x, y, z);

            lastScale = Selection.activeGameObject.transform.localScale;

            curSelectGo = Selection.activeGameObject;
        }
    }
}